import java.io.*;
import java.net.Socket;

public class StreamCopyThread extends Thread {
	private Socket inSock;
	private Socket outSock;
	private Socket packetSocket;
	private boolean done=false;
	private StreamCopyThread peer;
	// Debug flag
	private boolean debug=false;
	// Size of receive buffer
	public int bufSize;
	// Header to prepend to log messages
	private String header;
	// Used to synchronize the connection-handling threads
	private Object lock;
	// PacketBuffer
	private PacketBuffer pb;
	// Analyzer
	private Analyzer anal;
	// Pipe
	private PipedOutputStream pipeOut;
	
	public final static int SERVER_TO_CLIENT = 0;
	public final static int CLIENT_TO_SERVER = 1;
	// type 0 : Server To Client
	// type 1 : Client To Server
	private int type;
	
	// Log streams for output and error messages
	private PrintStream out;
	private PrintStream err;

	public StreamCopyThread(Socket inSock, Socket outSock, int bufSize, String header, boolean debug, Object lock, int type, int connectionNumber) {
		this.inSock=inSock;
		this.outSock=outSock;
		this.bufSize = bufSize;
		this.header = header;
		this.debug = debug;
		this.lock = lock;
		this.type = type;
		this.pb = new PacketBuffer("buffer-"+type()+"-"+connectionNumber+".log");
		this.pipeOut = new PipedOutputStream();
		try {
			this.pb.connectPipe(pipeOut);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		this.anal = new Analyzer(pb, "analyzer-"+type()+"-"+connectionNumber+".log");
	}

	public void run() {
		byte[] buf=new byte[bufSize];
		int count=-1;
		try {
			InputStream in=inSock.getInputStream();
			OutputStream out=outSock.getOutputStream();
			try {
				while(((count=in.read(buf))>0)&&!isInterrupted()) {
					out.write(buf,0,count);
					pipeOut.write(buf,0,count);
				}
			} catch(Exception xc) {
				if(debug) {
					// FIXME
					// It's very difficult to sort out between "normal"
					// exceptions (occuring when one end closes the connection
					// normally), and "exceptional" exceptions (when something
					// really goes wrong)
					// Therefore we only log exceptions occuring here if the debug flag
					// is true, in order to avoid cluttering up the log.
					err.println(header+":"+xc);
					xc.printStackTrace();
				}
			} finally {
				// The input and output streams will be closed when the sockets themselves
				// are closed.
				out.flush();
			}
		} catch(Exception xc) {
			err.println(header+":"+xc);
			xc.printStackTrace();
		}
		synchronized(lock) {
			done=true;
			try {
				if((peer==null)||peer.isDone()) {
					System.out.println("Close "+type);
					// Cleanup if there is only one peer OR
					// if _both_ peers are done
					anal.interrupt();
					pipeOut.close();
					pb.close();
					inSock.close();
					outSock.close();
				}
				else 
					// Signal the peer (if any) that we're done on this side of the connection
					peer.interrupt();
			} catch(Exception xc) {
				err.println(header+":"+xc);
				xc.printStackTrace();
			} finally {
				//connections.removeElement(this);
			}
		}
	}
	public boolean isDone() {
		return done;
	}

	public void setPeer(StreamCopyThread peer) {
		this.peer=peer;
	}
	
	private String type()
	{
		if (this.type == SERVER_TO_CLIENT)
			return "stc";
		else
			return "cts";
	}
	
	public String toString()
	{
		return "Thread " + type();
	}

}